package chan.project.service.impl;

import chan.project.constant.LocationConstant;
import chan.project.exception.BusinessException;
import chan.project.manager.AmapManager;
import chan.project.manager.QianFanManager;
import chan.project.model.dto.travelPlan.GeneratedRoute;
import chan.project.model.dto.travelPlan.TravelGuide;
import chan.project.model.entity.Location;
import chan.project.model.entity.TravelPlanThumb;
import chan.project.service.LocationService;
import chan.project.service.TravelPlanThumbService;
import chan.project.utils.AiChatUtil;
import chan.project.utils.routeTemplate.DijkstraRouteTemplate;
import chan.project.utils.routeTemplate.DynamicRouteTemplate;
import chan.project.utils.routeTemplate.RouteTemplate;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import chan.project.common.ErrorCode;
import chan.project.constant.CommonConstant;
import chan.project.exception.ThrowUtils;
import chan.project.mapper.TravelPlanMapper;
import chan.project.model.dto.travelPlan.TravelPlanQueryRequest;
import chan.project.model.entity.TravelPlan;
import chan.project.model.entity.User;
import chan.project.model.vo.TravelPlanVO;
import chan.project.model.vo.UserVO;
import chan.project.service.TravelPlanService;
import chan.project.service.UserService;
import chan.project.utils.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 旅行计划服务实现
 *
 * @author <a href="https://github.com/kavansp">kavansp</a>
 * 
 */
@Service
@Slf4j
public class TravelPlanServiceImpl extends ServiceImpl<TravelPlanMapper, TravelPlan> implements TravelPlanService {

    @Resource
    private UserService userService;

    @Resource
    private AmapManager amapManager;

    @Resource
    private LocationService locationService;

    @Resource
    private QianFanManager qianFanManager;

    @Resource
    private TravelPlanThumbService travelPlanThumbService;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 校验数据
     *
     * @param travelPlan
     * @param add      对创建的数据进行校验
     */
    @Override
    public void validTravelPlan(TravelPlan travelPlan, boolean add) {
        ThrowUtils.throwIf(travelPlan == null, ErrorCode.PARAMS_ERROR);
        //从对象中取值
        String planRequirements = travelPlan.getPlanRequirements();
        // 创建数据时，参数不能为空
        if (add) {
            ThrowUtils.throwIf(StringUtils.isBlank(planRequirements), ErrorCode.PARAMS_ERROR);
        }
    }

    /**
     * 获取查询条件
     *
     * @param travelPlanQueryRequest
     * @return
     */
    @Override
    public QueryWrapper<TravelPlan> getQueryWrapper(TravelPlanQueryRequest travelPlanQueryRequest) {
        QueryWrapper<TravelPlan> queryWrapper = new QueryWrapper<>();
        if (travelPlanQueryRequest == null) {
            return queryWrapper;
        }
        Long id = travelPlanQueryRequest.getId();
        Long notId = travelPlanQueryRequest.getNotId();
        String searchText = travelPlanQueryRequest.getSearchText();
        Long userId = travelPlanQueryRequest.getUserId();
        String planRequirements = travelPlanQueryRequest.getPlanRequirements();
        String generatedRoute = travelPlanQueryRequest.getGeneratedRoute();
        String generatedDescription = travelPlanQueryRequest.getGeneratedDescription();
        String sortField = travelPlanQueryRequest.getSortField();
        String sortOrder = travelPlanQueryRequest.getSortOrder();

        // 补充需要的查询条件
        // 从多字段中搜索
        if (StringUtils.isNotBlank(searchText)) {
            // 需要拼接查询条件
            queryWrapper.and(qw -> qw.like("planRequirements", searchText).or().like("generatedRoute", searchText));
        }
        // 模糊查询
        queryWrapper.like(StringUtils.isNotBlank(planRequirements), "planRequirements", planRequirements);
        queryWrapper.like(StringUtils.isNotBlank(generatedRoute), "generatedRoute", generatedRoute);
        queryWrapper.like(StringUtils.isNotBlank(generatedDescription), "generatedDescription", generatedDescription);
        // 精确查询
        queryWrapper.ne(ObjectUtils.isNotEmpty(notId) && notId != 0, "id", notId);
        queryWrapper.eq(ObjectUtils.isNotEmpty(id) && id != 0, "id", id);
        queryWrapper.eq(ObjectUtils.isNotEmpty(userId) && userId != 0, "userId", userId);
        // 排序规则
        queryWrapper.orderBy(SqlUtils.validSortField(sortField),
                sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }

    /**
     * 获取旅行计划封装
     *
     * @param travelPlan
     * @param request
     * @return
     */
    @Override
    public TravelPlanVO getTravelPlanVO(TravelPlan travelPlan, HttpServletRequest request) {
        // 对象转封装类
        TravelPlanVO travelPlanVO = TravelPlanVO.objToVo(travelPlan);
        Long userId = travelPlan.getUserId();
        User user = null;
        if (userId != null && userId > 0) {
            user = userService.getById(userId);
        }
        UserVO userVO = userService.getUserVO(user);
        travelPlanVO.setUser(userVO);;
        return travelPlanVO;
    }

    /**
     * 分页获取旅行计划封装
     *
     * @param travelPlanPage
     * @param request
     * @return
     */
    @Override
    public Page<TravelPlanVO> getTravelPlanVOPage(Page<TravelPlan> travelPlanPage, HttpServletRequest request) {
        List<TravelPlan> travelPlanList = travelPlanPage.getRecords();
        User loginUser = userService.getLoginUser(request);
        Page<TravelPlanVO> travelPlanVOPage = new Page<>(travelPlanPage.getCurrent(), travelPlanPage.getSize(), travelPlanPage.getTotal());
        if (CollUtil.isEmpty(travelPlanList)) {
            return travelPlanVOPage;
        }
        // 对象列表 => 封装对象列表
        List<TravelPlanVO> travelPlanVOList = travelPlanList.stream().map(travelPlan -> {
            Long userId = travelPlan.getUserId();
            User user = userService.getById(userId);

            TravelPlanVO travelPlanVO = TravelPlanVO.objToVo(travelPlan);
            travelPlanVO.setUser(UserVO.objToVo(user));
            return travelPlanVO;
        }).collect(Collectors.toList());
        // region 可选
        Set<Long> userIdSet = travelPlanList.stream().map(TravelPlan::getUserId).collect(Collectors.toSet());
        Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
                .collect(Collectors.groupingBy(User::getId));

        //查找当前用户点赞过的旅行计划
        LambdaQueryWrapper<TravelPlanThumb> travelPlanThumbLambdaQueryWrapper = new LambdaQueryWrapper<>();
        travelPlanThumbLambdaQueryWrapper.eq(TravelPlanThumb::getUserId,loginUser.getId());
        List<Long> travelPlanIdList = travelPlanThumbService.list(travelPlanThumbLambdaQueryWrapper).stream()
                .map(TravelPlanThumb::getTravelPlanId).collect(Collectors.toList());

        // 填充信息
        travelPlanVOList.forEach(travelPlanVO -> {
            travelPlanVO.setIsThumb(false);
            Long userId = travelPlanVO.getUser().getId();
            User user = null;
            if (userIdUserListMap.containsKey(userId)) {
                user = userIdUserListMap.get(userId).get(0);
            }
            travelPlanVO.setUser(userService.getUserVO(user));
            if(travelPlanIdList.contains(travelPlanVO.getId())){
                travelPlanVO.setIsThumb(true);
            }
        });
        // endregion
        travelPlanVOPage.setRecords(travelPlanVOList);
        return travelPlanVOPage;
    }


    private static String inputSystem = "你是一个严谨的旅游专家\n" +
            "我将会给你旅游地点的描述\n" +
            "以下是地点的数据格式，你输出的格式也需要严格按照以下结构来输出\n" +
            "\n" +
            "{\"name\":\"地点名称\",\"description\":\"地点描述\"}\n" +
            "\n" +
            "要求：如果传入的地点有地点名称没有地点描述就生成地点描述，如果没有地点名称只有地点描述就根据地点描述生成地点名称"+
            "注意：地点必须是国内的,地点名称要带上省市县等等，比如黄果树瀑布位于广东省深圳市南山区黄果树瀑布，但是不要在地点后面输出括号，比如浙江省舟山市普陀区普陀山南海观音像（可远眺大海）类似的信息,如果不是请对地点名称进行修改"+
            "其中的Name是地点名，description是地点描述。\n" +
            "注意：只需要输出该字符串，不要输出其它的任何文字";

    /**
     * 传入地点生成经纬度后选择最优路线
     * @param travelGuide
     * @return
     */
    @Override
    public TravelPlan generateTravelPlan(TravelGuide travelGuide) {
        //根据Ai生成的地点生成路线
        List<Location> locationList = travelGuide.getLocationList();
        String allDescription = travelGuide.getAllDescription();
        String planRequirements = travelGuide.getPlanRequirements();
        String startLocationName = travelGuide.getStartLocationName();
        String endLocationName = travelGuide.getEndLocationName();

        //为每个地点计算经纬度
        for (Location location : locationList) {
            String name = location.getName();
            String description = location.getDescription();
//            1.判断是否有空的列表
            if(StringUtils.isBlank(name)){
                if(StringUtils.isBlank(description)){
                    throw new BusinessException(ErrorCode.PARAMS_ERROR,"输入地点为空");
                }
                //地点为空描述非空
                //调用ai根据地点描述生成地点名称   注意异步调用
                String chatLocationJsonStr = qianFanManager.Chat(inputSystem + JSONUtil.toJsonStr(location));
                String locationJsonStr = AiChatUtil.convertFormat(chatLocationJsonStr);
                Location descriptionLocation = JSONUtil.toBean(locationJsonStr, Location.class);
                BeanUtil.copyProperties(descriptionLocation,location);
            } else if (StringUtils.isBlank(description)) {
                //地点非空描述为空
                //查询redis缓存
                Location desrveLocation = (Location) redisTemplate.opsForValue().get(LocationConstant.LOCATION_PREFIX + location.getName());
                if(desrveLocation == null) {
                    //缓存中没有数据，查询数据库
                    LambdaQueryWrapper<Location> locationLambdaQueryWrapper = new LambdaQueryWrapper<>();
                    locationLambdaQueryWrapper.eq(Location::getName, name);
                    desrveLocation = locationService.getOne(locationLambdaQueryWrapper);
                }
                if(desrveLocation == null){
                    //缓存与数据库都没有地点描述，调用ai根据地点名称生成描述
                    String chatLocationJsonStr = qianFanManager.Chat(inputSystem + JSONUtil.toJsonStr(location));
                    String locationJsonStr = AiChatUtil.convertFormat(chatLocationJsonStr);
                    desrveLocation = JSONUtil.toBean(locationJsonStr, Location.class);
                    //判断如果是起点或者终点需要修改为生成的地点
                    BeanUtil.copyProperties(desrveLocation,location);
                }
                BeanUtil.copyProperties(desrveLocation,location);
                //修改起点与终点的名称
                if(name.equals(startLocationName)){
                    startLocationName = desrveLocation.getName();
                }
                if(name.equals(endLocationName)){
                    endLocationName = desrveLocation.getName();
                }
            }
            //2.查找地点是否已经保存,设置经纬度
            //查询redis缓存
            Location savedLocation = (Location) redisTemplate.opsForValue().get(LocationConstant.LOCATION_PREFIX + location.getName());
            if(savedLocation == null) {
                //查询数据库
                LambdaQueryWrapper<Location> locationQueryWrapper = new LambdaQueryWrapper<>();
                locationQueryWrapper.eq(Location::getName, location.getName());
                savedLocation = locationService.getOne(locationQueryWrapper);
            }
            if(savedLocation != null){
                location.setCoordinates(savedLocation.getCoordinates());
            }else {
                //地点未保存，获得经纬度后进行保存
                Map<String, String> lonAndLat = amapManager.getLonAndLat(location.getName());
                location.setCoordinates(JSONUtil.toJsonStr(lonAndLat));
                boolean save = locationService.save(location);
                if(!save){
                    throw new BusinessException(ErrorCode.OPERATION_ERROR);
                }
            }
        }
        //3.对传入的所有地点进行选择性排序，返回的列表已经具有顺序
        //todo 根据是否有终点起点判断使用的策略
        //无起点与终点（则默认为寻找最小的环）
        List<Location> resultList = new ArrayList<>();
        if(StringUtils.isBlank(startLocationName) && StringUtils.isBlank(endLocationName)) {
            RouteTemplate dynamicRouteTemplate = new DynamicRouteTemplate();
            resultList = dynamicRouteTemplate.order(locationList);
        }
        else {
            List<Location> sequentiaList = new ArrayList<>();
            Location endLocation = new Location();
            //有起点与终点
            for (Location location : locationList) {
                if (startLocationName.equals(location.getName())) {
                    sequentiaList.add(0, location);
                }
                else if (endLocationName.equals(location.getName())) {
                    endLocation = location;
                }else {
                    sequentiaList.add(location);
                }
            }
            sequentiaList.add(endLocation);
            RouteTemplate routeTemplate = new DijkstraRouteTemplate();
            resultList = routeTemplate.order(sequentiaList);
        }

        //保存路线
        List<GeneratedRoute> generatedRouteList = new ArrayList<>();
        for (Location location : resultList) {
            GeneratedRoute generatedRoute = new GeneratedRoute();
            generatedRoute.setLocationName(location.getName());
            generatedRoute.setCoordinates(location.getCoordinates());
            generatedRoute.setDescription(location.getDescription());
            generatedRouteList.add(generatedRoute);
        }
        //存储返回计划
        TravelPlan travelPlan = new TravelPlan();
        travelPlan.setGeneratedRoute(JSONUtil.toJsonStr(generatedRouteList));
        travelPlan.setPlanRequirements(planRequirements);//计划需求如何传递
        travelPlan.setGeneratedDescription(allDescription);
        return travelPlan;
    }
}
